/* eslint-disable */
/**
 * @file gitdiff 消息解析器
 * @author errorrik(errorrik@gmail.com)
 */

(function (root) {
  const STAT_START = 2;
  const STAT_FILE_META = 3;
  const STAT_HUNK = 5;

  const parser = {
    /**
     * 解析 gitdiff 消息
     *
     * @param {string} source gitdiff消息内容
     * @return {Object}
     */
    parse(source) {
      const infos = [];
      let stat = STAT_START;
      let currentInfo = {};
      let currentHunk = {
        changes: [],
      };
      let changeOldLine;
      let changeNewLine;

      const lines = source.split('\n');
      const linesLen = lines.length;
      let i = 0;

      while (i < linesLen) {
        const line = lines[i];

        if (line.indexOf('diff --git') === 0) {
          const filesStr = line.slice(11);
          let oldPath = null;
          let newPath = null;

          const quoteIndex = filesStr.indexOf('"');
          switch (quoteIndex) {
            case -1:
              var segs = filesStr.split(' ');
              oldPath = segs[0].slice(2);
              newPath = segs[1].slice(2);
              break;

            case 0:
              var nextQuoteIndex = filesStr.indexOf('"', 2);
              oldPath = filesStr.slice(3, nextQuoteIndex);
              var newQuoteIndex = filesStr.indexOf('"', nextQuoteIndex + 1);
              if (newQuoteIndex < 0) {
                newPath = filesStr.slice(nextQuoteIndex + 4);
              } else {
                newPath = filesStr.slice(newQuoteIndex + 3, -1);
              }
              break;

            default:
              var segs = filesStr.split(' ');
              oldPath = segs[0].slice(2);
              newPath = segs[1].slice(3, -1);
              break;
          }

          // read file
          currentInfo = {
            oldPath,
            newPath,
            hunks: [],
          };

          infos.push(currentInfo);

          // 1. 如果oldPath是/dev/null就是add
          // 2. 如果newPath是/dev/null就是delete
          // 3. 如果有 rename from foo.js 这样的就是rename
          // 4. 如果有 copy from foo.js 这样的就是copy
          // 5. 其它情况是modify
          let currentInfoType = null;

          // read mode change
          let nextLine = lines[i + 1];
          if (nextLine.indexOf('old') === 0) {
            currentInfo.oldMode = nextLine.slice(9, 16);
            currentInfo.newMode = lines[i + 2].slice(9, 16);
            i += 2;
            nextLine = lines[i + 1];
          }

          // read similarity
          if (nextLine.indexOf('similarity') === 0) {
            currentInfo.similarity = parseInt(nextLine.split(' ')[2], 10);
            i += 1;
          }

          // 用 --- 、 +++ 这两行作为oldPath、newPath，更准确
          // 否则当文件名有空格时，由于前端拼了假头，会解析出错
          var pathLine;
          let j = i;
          while ((pathLine = lines[++j])) {
            if (pathLine.indexOf('--- ') === 0) {
              // 如果是用引号包裹的，去掉引号
              if (pathLine.length && pathLine[4] === '"' && pathLine[pathLine.length - 1] === '"') {
                currentInfo.oldPath = pathLine.slice(7);
                currentInfo.oldPath = currentInfo.oldPath.slice(0, currentInfo.oldPath.length - 1);

                // 如果有转义的引号，去掉转义
                if (currentInfo.oldPath.indexOf('"') !== -1) {
                  currentInfo.oldPath = currentInfo.oldPath.replace('\\"', '"');
                }
              } else {
                currentInfo.oldPath = pathLine.slice(6);
              }
            }

            if (pathLine.indexOf('+++ ') === 0) {
              // 如果是用引号包裹的，去掉引号
              if (pathLine.length && pathLine[4] === '"' && pathLine[pathLine.length - 1] === '"') {
                currentInfo.newPath = pathLine.slice(7);
                currentInfo.newPath = currentInfo.newPath.slice(0, currentInfo.newPath.length - 1);

                // 如果有转义的引号，去掉转义
                if (currentInfo.newPath.indexOf('"') !== -1) {
                  currentInfo.newPath = currentInfo.newPath.replace('\\"', '"');
                }
              } else {
                currentInfo.newPath = pathLine.slice(6);
              }
              break;
            }
          }

          // read similarity type and index
          var simiLine;
          simiLoop: while ((simiLine = lines[++i])) {
            var segs = simiLine.split(' ');

            switch (segs[0]) {
              case 'diff': // diff --git
                i--;
                break simiLoop;

              case 'index':
                var revs = segs[1].split('..');
                currentInfo.oldRevision = revs[0];
                currentInfo.newRevision = revs[1];

                if (segs[2]) {
                  currentInfo.oldMode = currentInfo.newMode = segs[2];
                }
                stat = STAT_HUNK;

                var oldFileName = lines[i + 1];
                if (oldFileName.indexOf('---') === 0) {
                  const newFileName = lines[i + 2];

                  if (/\s\/dev\/null$/.test(oldFileName)) {
                    currentInfo.oldPath = '/dev/null';
                    currentInfoType = 'add';
                  } else if (/\s\/dev\/null$/.test(newFileName)) {
                    currentInfo.newPath = '/dev/null';
                    currentInfoType = 'delete';
                  }

                  i += 2;
                }

                break simiLoop;
            }

            if (!currentInfoType) {
              currentInfoType = segs[0];
            }
          }

          currentInfo.type = currentInfoType || 'modify';
        } else if (line.indexOf('Binary') === 0) {
          currentInfo.isBinary = true;
          stat = STAT_START;
          currentInfo = null;
        } else if (stat === STAT_HUNK) {
          if (line.indexOf('@@') === 0) {
            const match = /^@@\s+-([0-9]+)(,([0-9]+))?\s+\+([0-9]+)(,([0-9]+))?/.exec(line);

            currentHunk = {
              content: line.slice(0, line.lastIndexOf('@@') + 2),
              oldStart: match[1] - 0,
              newStart: match[4] - 0,
              oldLines: match[3] - 0 || 1,
              newLines: match[6] - 0 || 1,
              changes: [],
            };

            currentInfo.hunks.push(currentHunk);
            changeOldLine = currentHunk.oldStart;
            changeNewLine = currentHunk.newStart;
          } else {
            const typeChar = line.slice(0, 1);
            const change = {
              content: line.slice(1),
            };

            switch (typeChar) {
              case '+':
                change.type = 'insert';
                change.isInsert = true;
                change.lineNumber = changeNewLine;
                change.oldLineNumber = changeOldLine;
                change.newLineNumber = changeNewLine;
                changeNewLine++;
                break;

              case '-':
                change.type = 'delete';
                change.isDelete = true;
                change.lineNumber = changeOldLine;
                change.oldLineNumber = changeOldLine;
                change.newLineNumber = changeNewLine;
                changeOldLine++;
                break;

              case ' ':
                change.type = 'normal';
                change.isNormal = true;
                change.oldLineNumber = changeOldLine;
                change.newLineNumber = changeNewLine;
                changeOldLine++;
                changeNewLine++;
                break;
            }
            change.type && currentHunk.changes.push(change);
          }
        }

        i++;
      }
      return infos;
    },
  };

  if (typeof exports === 'object' && typeof module === 'object') {
    // For CommonJS
    exports = module.exports = parser;
  } else if (typeof define === 'function' && define.amd) {
    // For AMD
    define('gitDiffParser', [], parser);
  } else {
    root.gitDiffParser = parser;
  }
})(this);
